import java.awt.Color;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import ij.IJ;
import ij.ImagePlus;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;

public class TextureDetection implements PlugInFilter {

	private int blockSize;
	private ImageProcessor imageProcessor;

	@Override
	public void run(ImageProcessor ip) {
		//TODO: zakodowana wielkosc bloku
		blockSize = 64;
		imageProcessor = ip;
		// podzial na bloki
		Collection<Block> blocks = divideToBlocks();
		// obliczeniu kryterium dla kazdego bloku
		calculateCriteria(blocks);
		// przydzial blokow do regionow na podstawie w/w kryterium
		Collection<Region> regions = allocateRegions(blocks);
		// dla kazdego bloku w regionie obliczenie macierzy spojnosci
		for (Region region : regions) {
			//TODO: liczona jest tylko 1 macierz na blok (1 kierunek, 1 odleglosc)
			for (Block block : region.blocks)
			{
				block.matrices.add(block.calculateCoocurenceMatrix(8, 0, 1));
			}
			//TODO: skonsolidowanie macierzy z poszczegolnych blokow regionu do macierzy regionu 
		}
	}

	@Override
	public int setup(String arg, ImagePlus imp) {
		if (arg.equals("about")) {
			showAbout();
			return DONE;
		}
		return DOES_ALL;
	}

	private void showAbout() {
		// TODO: info dialog
	}

	private Collection<Region> allocateRegions(Collection<Block> blocks) {
		//TODO: zaklada sie ze wartosci critertiaValue zawarte sa miedzy 0-1 i progujemy je na 16 wartosci
		HashMap<Integer, Region> regionMap = new HashMap<Integer, Region>();
		//double v = 1 / 31.0f;
		int r;
		for (Block block : blocks) {
			//r = (int) (block.critertiaValue / v);
			r = levelPixelByBrightness((float)block.critertiaValue, 16);
			if (!regionMap.containsKey(r))
				regionMap.put(r, new Region());
			regionMap.get(r).blocks.add(block);
		}
		return regionMap.values();
	}

	private void calculateCriteria(Collection<Block> blocks) {
		//TODO: zakodowane kryterium jednorodnosci jako srednia jasnosc
		int blockPixelCount;
		float sum;
		Color color;
		for (Block block : blocks) {
			sum = 0;
			blockPixelCount = block.getPixelCount();
			for (int i = 0; i < blockPixelCount; ++i) {
				/*color = new Color(block.getPixel(i));
				sum += Color.RGBtoHSB(color.getRed(), color.getGreen(),
						color.getBlue(), null)[2];*/
				sum += getPixelBrightness(block.getPixel(i));
			}
			block.critertiaValue = sum / blockPixelCount;
		}
	}
	
	private float getPixelBrightness(int pixelValue)
	{
		Color color = new Color(pixelValue);
		return Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null)[2];
	}
	
	private int levelPixelByBrightness(float brightness, int levels)
	{
		double v = 1.0f / (levels-1);
		return (int) (brightness / v);
	}

	private Collection<Block> divideToBlocks() {
		LinkedList<Block> blockList = new LinkedList<Block>();
		int x, y, w, h;
		TextureDetection.Block b;
		y = 0;
		while (y < imageProcessor.getHeight()) {
			x = 0;
			while (x < imageProcessor.getWidth()) {
				w = ((blockSize + x) < imageProcessor.getWidth()) ? blockSize
						: imageProcessor.getWidth() - x;
				h = ((blockSize + y) < imageProcessor.getHeight()) ? blockSize
						: imageProcessor.getHeight() - y;
				blockList.add(new TextureDetection.Block(x, y, w, h));
				x += blockSize;
			}
			y += blockSize;
		}
		return blockList;
	}

	private class Block {
		Collection<CoocurenceMatrix> matrices;
		Rectangle imageArea;
		double critertiaValue;

		public Block() {
			imageArea = new Rectangle();
			matrices = new LinkedList<CoocurenceMatrix>();
		}

		public Block(int x, int y, int width, int height) {
			imageArea = new Rectangle(x, y, width, height);
			matrices = new LinkedList<CoocurenceMatrix>();
		}

		public int getPixelCount() {
			return imageArea.width * imageArea.height;
		}

		public int getPixel(int index) {
			if (index > getPixelCount())
				return 0;
			int x = index % imageArea.width;
			int y = index / imageArea.height;
			x += imageArea.x;
			y += imageArea.y;
			return imageProcessor.getPixel(x, y);
		}
		
		public int getPixelX(int index)
		{
			return (index % imageArea.width)+imageArea.x;
		}
		
		public int getPixelY(int index)
		{
			return (index % imageArea.height)+imageArea.y;
		}
		
		public CoocurenceMatrix calculateCoocurenceMatrix(int levels, int direction, int distance)
		{
			CoocurenceMatrix matrix = new CoocurenceMatrix();
			matrix.values = new int[levels][levels];
			int x,y;
			for(int i=0;i<getPixelCount();++i)
			{
				//TODO: czy jako sasiedzi liczone sa tez piksele nie nalezace do bloku?
				x = getPixelX(i);
				y = getPixelY(i);
				//TODO: inne kierunki, na razie jest tylko poziomy
				matrix.values[levelPixelByBrightness(getPixelBrightness(getPixel(i)),levels)][levelPixelByBrightness(getPixelBrightness(imageProcessor.getPixel(x+distance, y)),levels)]++;
				matrix.values[levelPixelByBrightness(getPixelBrightness(getPixel(i)),levels)][levelPixelByBrightness(getPixelBrightness(imageProcessor.getPixel(x-distance, y)),levels)]++;
			}
			return matrix;
		}
	}

	private class Region {
		Collection<Block> blocks;

		public Region() {
			blocks = new LinkedList<Block>();
		}
	}
	
	private class CoocurenceMatrix
	{
		int values[][];
		
		public CoocurenceMatrix()
		{
			
		}
	}
}
